package ru.nsu.fit.iconveyor.conveyor.storage.exporter.graphml;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import ru.nsu.fit.iconveyor.conveyor.Conveyor;
import ru.nsu.fit.iconveyor.conveyor.Globals;
import ru.nsu.fit.iconveyor.conveyor.node.Node;
import ru.nsu.fit.iconveyor.conveyor.node.NodeConnection;
import ru.nsu.fit.iconveyor.conveyor.storage.Utils;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Map;
import java.util.Queue;

/**
 * This class make export to graphml file from class conveyor.
 *
 * @author Timur Zolotuhin (tzolotuhin@gmail.com)
 */
public class GraphMLExporter {
	public static void saveGraphML(String fileName, Conveyor conveyor) throws Exception {
		FileOutputStream out = null;
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
	
			// root elements
			Document doc = docBuilder.newDocument();
			Element rootElement = doc.createElement("graphml");
			rootElement.setAttribute("xmlns", "http://graphml.graphdrawing.org/xmlns");
			rootElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
			rootElement.setAttribute("xsi:schemaLocation", "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd");
			doc.appendChild(rootElement);

            // get nodes and channels
            Collection<Node> nodes = conveyor.getNodes();
            Collection<String> channelIds = conveyor.getChannelIds();

            // save keys
            Queue<String> vertexAttr = new ArrayDeque<String>();
            vertexAttr.add(Globals.EXECUTOR_ATTR_NAME);
            for (Node node : nodes) {
                Map<String, Object> parameters = node.getParameters();
                for (String parameterName : parameters.keySet()) {
                    vertexAttr.add(Utils.generateKeyForNodeAttribute(node.getId(), parameterName));
                }
            }


            Queue<String> edgeAttr = new ArrayDeque<String>();
            edgeAttr.add(Globals.INPUT_ATTR_NAME);
            edgeAttr.add(Globals.OUTPUT_ATTR_NAME);

			for (String buf : vertexAttr) {
				Element key_node = doc.createElement("key");
				key_node.setAttribute("id", buf);
				key_node.setAttribute("for", "node");
				key_node.setAttribute("attr.name", buf);
				key_node.setAttribute("attr.type", "string");
				rootElement.appendChild(key_node);
			}
			
			for (String buf : edgeAttr) {
				Element key_node = doc.createElement("key");
				key_node.setAttribute("id", buf);
				key_node.setAttribute("for", "edge");
				key_node.setAttribute("attr.name", buf);
				key_node.setAttribute("attr.type", "string");
				rootElement.appendChild(key_node);
			}

            // save graph info
            Element graph_node = doc.createElement("graph");
            graph_node.setAttribute("id", "graph_root");
            graph_node.setAttribute("edgedefault", "directed");
            rootElement.appendChild(graph_node);

            // save vertex
            if (nodes != null && nodes.size() > 0) {
                for (Node buf : nodes) {
                    Element node = doc.createElement("node");
                    node.setAttribute("id", buf.getId());
                    graph_node.appendChild(node);

                    Element data_node = doc.createElement("data");
                    data_node.setAttribute("key", Globals.EXECUTOR_ATTR_NAME);
                    data_node.appendChild(doc.createTextNode(buf.getClass().getName()));
                    node.appendChild(data_node);

                    Map<String, Object> parameters = buf.getParameters();
                    for (String parameterName : parameters.keySet()) {
                        data_node = doc.createElement("data");
                        data_node.setAttribute("key", Utils.generateKeyForNodeAttribute(buf.getId(), parameterName));
                        data_node.appendChild(doc.createTextNode(parameters.get(parameterName).toString()));
                        node.appendChild(data_node);
                    }
                }
            }

            // save edge
            if (channelIds != null && channelIds.size() > 0) {
                for (String channelId : channelIds) {
                    String srcId = null, trgId = null;
                    NodeConnection srcConnection = null, trgConnection = null;
                    for (Node node : nodes) {
                        Collection<NodeConnection<?>> nodeConnections = node.getNodeConnections();
                        if (nodeConnections != null) {
                            for (NodeConnection<?> nodeConnection : nodeConnections) {
                                if (nodeConnection.getChannelId() != null && nodeConnection.getChannelId().equals(channelId)) {
                                    if (nodeConnection.getType() == NodeConnection.Type.INPUT) {
                                        trgId = node.getId();
                                        trgConnection = nodeConnection;
                                    } else {
                                        srcId = node.getId();
                                        srcConnection = nodeConnection;
                                    }
                                }
                            }
                        }
                    }

                    if (srcId != null && trgId != null) {
                        Element channelNode = doc.createElement("edge");
                        channelNode.setAttribute("id", channelId);
                        channelNode.setAttribute("source", srcId);
                        channelNode.setAttribute("target", trgId);
                        graph_node.appendChild(channelNode);

                        Element inputAttrNode = doc.createElement("data");
                        inputAttrNode.setAttribute("key", Globals.INPUT_ATTR_NAME);
                        inputAttrNode.appendChild(doc.createTextNode(trgConnection.getName()));
                        channelNode.appendChild(inputAttrNode);

                        Element outputAttrNode = doc.createElement("data");
                        outputAttrNode.setAttribute("key", Globals.OUTPUT_ATTR_NAME);
                        outputAttrNode.appendChild(doc.createTextNode(srcConnection.getName()));
                        channelNode.appendChild(outputAttrNode);
                    }
                }
            }
			
			// write the content into xml file
			OutputFormat format = new OutputFormat(doc);
			format.setIndenting(true);
	
			// to generate a file output use fileoutputstream instead of system.out
			out = new FileOutputStream(new File(fileName));
			XMLSerializer serializer = new XMLSerializer(out, format);
			serializer.serialize(doc);
		} finally {
            if (out != null)
                out.close();
		}
	}
}
